package de.fhaugsburg.games.boardgames;

import java.util.Iterator;

import de.fhaugsburg.games.boardgames.exceptions.OutsideBoardException;
import de.fhaugsburg.games.boardgames.scrabble.scrabbleboard.IPiece;



public class Board <P extends IPiece, D> {
	
	
	
	
	

	
	/**
	 * iterator factory to walk in given direction
	 * @param startPoint
	 * @param direction
	 * @param startPointInclusive
	 * @return
	 */
	public Iterator<GridPoint> iterator(GridPoint startPoint, GridPoint direction, boolean startPointInclusive){
		return new DirIterator(startPoint, direction, startPointInclusive);
	}

	
	
	/**
	 * Iterator class to walk along a line of pieces/GridPoints
	 */
	private class DirIterator implements Iterator<GridPoint>{
		private GridPoint startPoint, direction, currentLocation;
		
		public DirIterator(GridPoint startPoint, GridPoint direction, boolean startPointInclusive) {
			super();
			this.startPoint = startPoint;
			this.direction = direction;
			this.currentLocation = startPoint;
			if (!startPointInclusive && hasNext()){
				next();
			}
		}
		/**
		 * is our next location still on the board?
		 */
		public boolean hasNext() {
			if (isOnTheBoard(this.currentLocation.plus(this.direction))){
				return true;
			}else{
				return false;
			}
		}

		/**
		 * walk one GridPoint in given location (Exception needed?)
		 */
		public GridPoint next() {
			return this.currentLocation = this.currentLocation.plus(this.direction);
		}

		/**
		 * we do not want to remove anything
		 */
		public void remove() {
			throw new UnsupportedOperationException();
		}
	}
	
	
	
	
	
	
	
	private Square<P,D>[][] squares;
	private int rows, columns;
	private int movesMade = 0;
	
	
	
	
	/**
	 * Constructor
	 * @param rows
	 * @param columns
	 */
	public Board(int rows, int columns) {
		this.rows = rows;
		this.columns = columns;
		this.squares = new Square[rows][columns];
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < columns; j++) {
				this.squares[i][j] = new Square<P,D>();
			}
		}
	}

	/**
	 * clears the entire board
	 */
	public void reset() {
		for (int i = 0; i < this.rows; i++) {
			for (int j = 0; j < this.columns; j++) {
				this.squares[i][j].removePiece();				
			}
		}
	}

	/**
	 * drop a piece in the board
	 * @param piece
	 * @param row
	 * @param column
	 */
	public void putPiece(P piece, int row, int column) {
		if (isOnTheBoard(row, column)){
				this.squares[row][column].putPiece(piece);
				increaseMovesMade();
		}else{
			throw new OutsideBoardException("row=" + row + " column=" + column + " is not on the board");
		}
	}

	
	/**
	 * override to work with gridpoints
	 * @param piece
	 * @param point
	 */
	public void putPiece(P piece, GridPoint point){
		putPiece(piece, point.getRow(), point.getColumn());
	}
	
	
	/**
	 * read position, return the piece found, or null
	 * @param row
	 * @param column
	 * @return
	 */
	public P getPiece(int row, int column) {
		if (isOnTheBoard(row, column)){
			return this.squares[row][column].getPiece();
		}
		return null;
	}

	/**
	 * reads current position with given gridpoint
	 * @param point
	 * @return
	 */
	public P getPiece (GridPoint point) {
		if (isOnTheBoard(point)){
			return this.getPiece(point.getRow(), point.getColumn());
		}else{
			throw new OutsideBoardException();
		}
	}
	
	
	
	/**
	 * move pieces around
	 * @param fromRow
	 * @param fromColumn
	 * @param toRow
	 * @param toColumn
	 */
	public void movePiece(int fromRow, int fromColumn, int toRow, int toColumn) {
		if (isOnTheBoard(fromRow, fromColumn) && isOnTheBoard(toRow, toColumn)){
			this.squares[toRow][toColumn].putPiece(this.squares[fromRow][fromColumn].removePiece());
		}
	}

	
	/**
	 * removes a piece from given position
	 * @param row
	 * @param column
	 */
	public P removePiece(int row, int column) {
		if (isOnTheBoard(row, column)){
			return this.squares[row][column].removePiece();
		}else{
			throw new OutsideBoardException();
		}
	}

	
	/**
	 * override to remove piece at given GridPoint
	 * @param point
	 */
	public P removePiece(GridPoint point){
		return removePiece(point.getRow(), point.getColumn());
	}
	
	
	
	/**
	 * checks, if given coordinates are on the board
	 * @param row
	 * @param column
	 * @return
	 */
	public boolean isOnTheBoard(int row, int column) {
		if ((row < this.rows) && (column < this.columns) && (row >= 0) && (column >= 0)){
			return true;
		}else{
			return false;
		}
	}
	
	/**
	 * checks, if given GridPoint is on the board 
	 * @param point
	 * @return
	 */
	public boolean isOnTheBoard(GridPoint point) {
		if ((point.getRow() < this.rows) && (point.getColumn() < this.columns) && (point.getRow() >= 0) && (point.getColumn() >= 0)){
			return true;
		}else{
			return false;
		}
	}
	
	

	
	/**
	 * checks, if square is empty
	 * @param row
	 * @param column
	 * @return
	 */
	public boolean isEmpty(int row, int column) {
		if (isOnTheBoard(row, column)){
			if (this.squares[row][column].getPiece() == null){
				return true;
			}else{
				return false;
			}
		}
		return false;
	}

	
	/**
	 * override to work with points
	 * @param point
	 * @return
	 */
	public boolean isEmpty(GridPoint point){
		return isEmpty(point.getRow(), point.getColumn());
	}
	
	


	/**
	 * read dimension
	 * @return
	 */
	public int getHeight() {
		return this.rows;
	}

	
	
	/**
	 * read dimension
	 * @return
	 */
	public int getWidth() {
		return this.columns;
	}

	
	/**
	 * returns board as string-representation
	 */
	public String toString() {
		StringBuilder rs = new StringBuilder();
		for (int i = this.rows-1; i >= 0; i--) {
			for (int j = 0; j < this.columns; j++) {
				if (this.squares[i][j].getPiece() != null){
					rs.append(this.squares[i][j].getPiece().toString());
				}else{
					rs.append(".");
				}
				rs.append(" ");
			}
			rs.append("\n");
		}
		return rs.toString();
	}

	
	
	/**
	 * counts the moves
	 */
	private void increaseMovesMade() {
		this.movesMade++;
	}

	
	
	/**
	 * return number of moves made in the game
	 * @return
	 */
	public int getMovesMade() {
		return movesMade;
	}


	
	/**
	 * get details of given square
	 * @param row
	 * @param column
	 * @return
	 */
	public D getDetails(int row, int column){
		if (!isOnTheBoard(row, column))
			throw new OutsideBoardException();
		return this.squares[row][column].getDetails();
	}
		
	
	/**
	 * override to work with GridPoints
	 * @param point
	 * @return
	 */
	public D getDetails(GridPoint point){
		return getDetails(point.getRow(), point.getColumn());
	}
	
	
	
	/**
	 * set details of given square
	 * @param details
	 * @param row
	 * @param column
	 */
	public void setDetails(D details, int row, int column){
		if (!isOnTheBoard(row, column))
			throw new OutsideBoardException();
		this.squares[row][column].setDetails(details);
	}
	
	
	
	/**
	 * removes the details set up for given square
	 * @param row
	 * @param column
	 */
	public void removeDetails(int row, int column){
		if (!isOnTheBoard(row, column))
			throw new OutsideBoardException();
		this.squares[row][column].removeDetails();
	}
	
	
	/**
	 * count equal pieces in given direction
	 * @param piece
	 * @param startPoint
	 * @param direction
	 * @return
	 */
	public int countPiecesInDirection(P piece, GridPoint startPoint, GridPoint direction) {
		Iterator<GridPoint> it = this.iterator(startPoint, direction, true);
		int counter=0;
		while (it.hasNext()){
			if (this.getPiece(it.next()).equals(piece)){
				counter++;
			}else{
				return counter;
			}
		}
		return 0;
	}
	
	
	/**
	 * Checks if Board is equal with another Board
	 */
	public boolean equals(Board<P,D> cmpBoard) {
		
		for (int i=0; i<this.squares.length; i++) {
			for (int j=0; j<this.squares[i].length; j++) {
				
				// check every Piece
				if (! (squares[i][j].getPiece() == cmpBoard.squares[i][j].getPiece())) {
					return false;
				}
				
				// check every detail
				//if (! (squares[i][j].getDetails().equals(cmpBoard.squares[i][j].getDetails()))) {
				//	return false;
				//}
				
			}
		}
		
		return true;
	}
	
		
	
	
}